iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
Modern Web

Fastify 101系列 第 17

[Fastify] Day17 - Validation and Serialization with Typebox

  • 分享至 

  • xImage
  •  

大家好 我是 Yubin

當一個 Request 進來,在進入 route handler 之前,他的 Payload 應該要被經過驗證 (Validation)。
當一個 Response 要出去之前,他的 Payload 要經過序列化 (Serialization)。

本篇文章來介紹 Fastify 的 Validation 和 Serialization,以及如何透過 Typebox 搭配 TypeScript 來快速的定義 Schema,享受強型別帶給我們的諸多好處。


JSON Schema

Fastify 建議大家利用 JSON Schema 來進行驗證及序列化。
程式內部會將 schema 編譯為高效能的函式,藉此增加執行效率。

要注意的是,只有在 Content-Type 為 application-json 的時候,才會啟動 validation 機制。


Validation 使用 ajv 來驗證 Request。

Serialization 使用 fast-json-stringify 來序列化輸出。


Validation

要在 Fastify App 中 Validation 機制,要先定義好 Schema:

const bodySchema = {
  type: 'object',
  required: ['name', 'status'],
  properties: {
    name: { type: 'string' },
    description: { type: 'string' },
    status: { type: 'boolean' }
  }
}

然後定義 route option:

const routeOptions = {
  schema: {
    body: bodySchema
  }
}

接著就可以把這個 option 帶入想要進行 Validation 的 route:

server.post('/', routeOptions, (request, reply) => {
  // ...  
  return reply.status(200).send({ message: 'Hello World' })
})

這樣就可以對每個 POST / 的 request 進行 payload 的驗證,以下觀察幾個 request 的反應。


https://ithelp.ithome.com.tw/upload/images/20221002/20151148Az2WcZxGhk.jpg

上圖沒帶 Payload,Fastify 回應狀態碼 400 (Bad Request),也一並回覆的 message 給 client 端。


https://ithelp.ithome.com.tw/upload/images/20221002/20151148080X4L4Sg6.jpg

上圖有帶 JSON Payload,但因為沒帶 status,所以沒有通過 Validation 的檢查,一樣得到 400 的回應。


先定義 Schema,然後設定特定 route 的 body 要符合某種 Schema。

這樣一來,可以確保進到 route handler 的時候,request.body 的結構都是我們預期的 (因為不符合的 request 都已經在上一個生命週期中被檢查到,回傳 400 的狀態碼出去了)。

除了 body 外,還可以定義 querystringparamsheaders 的 Schema。

如下 routeOptions 定義:

const routeOptions: RouteShorthandOptions = {
  schema: {
    body: bodySchema,
    querystring: queryStringSchema,
    params: paramsSchema,
    headers: headersSchema
  }
}

Typebox

除了上面提到的定義 JSON-Schema 的方式,一個我自己很喜歡用的方式是透過 Typebox 來定義。

Typebox 是一個 JSON Schema Type Builder,可以幫我們產生 JSON Schema (要手打 JSON Schema 很累,因為很多是字串,非常可能會出錯)。

安裝 Typebox:

npm i @sinclair/typebox

剛剛定義的 Schema 可以改寫成這樣:

import { Type } from '@sinclair/typebox'

const schemaByTypebox = Type.Object({
  name: Type.String(),
  description: Type.Optional(Type.String()),
  status: Type.String()
})

有沒有覺得乾淨許多,而且藉由 TypeScript 的特性與 TypeScript Engine 搭配編輯器的提示,可以做到自動完成及型態檢查,更不用擔心有打錯字的風險。


程式如下:

import { Type } from '@sinclair/typebox'

const server: FastifyInstance = fastify()

const schemaByTypebox = Type.Object({
  name: Type.String(),
  description: Type.Optional(Type.String()),
  status: Type.String()
})

const routeOptions = {
  schema: {
    body: schemaByTypebox
  }
}

server.post('/', routeOptions, (request, reply) => {
  // ...

  return reply.status(200).send({ message: 'Hello World' })
})

Serialization

我們已經知道 Fastify 的驗證及序列化跟 JSON Schema 脫離不了關係,也知道如何定義 JSON Schema 及透過 Typebox 協助我們定義 JSON Schema。

Serialization 這邊的運作也是一樣。

定義好 response 的 Schema 之後,設定到某個 route option 上,並且可以針對不同的狀態碼進行 Schema 的指定。


假設我希望 GET /,在回應狀態碼 200 的情況下,
回應的 Payload 要有一個 message 的欄位,且該欄位的型態是 string

定義 JSON Schema (使用 Typebox):

const getResponseSchema = Type.Object({
  message: Type.String()
})

設定 response status code 是 200 的時候,套用該 Schema:

const getRouteOption = {
  schema: {
    response: {
      200: getResponseSchema
    }
  }
}

這樣就定義好輸出的格式了。

如果程式實作中,沒有輸出相應格式的 Payload,如:

server.get('/', getRouteOption, (request, reply) => {
  return reply.status(200).send({ msg: 'Hello World' })
})

https://ithelp.ithome.com.tw/upload/images/20221002/201511484PBexxEhGY.jpg

client 端會收到 500 的狀態碼,因為 server 沒有如期的回應事先定義好的欄位格式。


定義 Schema 來做 Validation 跟 Serialization 的好處是,可以確保進來的 request 都是符合預期的。

如果 client 送給 server 的 request 沒有符合預期,會回覆 client 端的錯誤 (如 400 Bad Request)。
如果 server 回應給 client 的 response 沒有符合預期,則回覆 server 端的錯誤 (如 500 Internal Server Error),藉此劃分好各自的責任。

這樣一來,
只要 server 收到 request,就可以確保格式是正確的。
只要 client 端收到成功的狀態碼,就可以確保收到的格式是符合預期的。

前後端溝通,定義好 API 的規格後,把那些規格實作在程式中的驗證及序列化機制,讓前後端的合作可以更加順暢。


補充:

Fastify 官方文件中講了很多 ajv 及 JSON Schema 的細節,但自己在使用上還是覺得 Typebox 使用起來更為順手程式也更乾淨。
除非是先已經在設計階段定義好 JSON Schema,不然建議大家使用 Typebox 來實作會讓開發體驗更加良好。


上一篇
[Fastify] Day16 - Content-Type Parser
下一篇
[Fastify] Day18 - Testing
系列文
Fastify 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言